#region File Description
//-----------------------------------------------------------------------------
// ParticleSystem.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
#endregion

namespace Stonecode.Solar.ParticleEngine.ParticleSystems
{
    /// <summary>
    /// The main component in charge of displaying particles.
    /// </summary>
    public class ParticleSystem
    {
        #region Fields

        public bool ConstrainZ = false;

        // Settings class controls the appearance and animation of this particle system.
        private EmitterSettings settings; // = new ParticleSettings();
        public EmitterSettings Settings { get { return settings; } }

        private Vector3 _offset;

        private Texture2D texture;
        public Texture2D Texture { get { return texture; } }

        // Custom effect for drawing point sprite particles. This computes the particle
        // animation entirely in the vertex shader: no per-particle CPU work required!
        Effect particleEffect;
        public Effect ParticleEffect { get { return particleEffect; } }

        // Shortcuts for accessing frequently changed effect parameters.
        EffectParameter effectViewParameter;
        EffectParameter effectProjectionParameter;
        EffectParameter effectViewportScaleParameter;
        EffectParameter effectTimeParameter;
        EffectParameter effectForcesTimeParameter;
        EffectParameter effectGravityParameter;
        EffectParameter effectLastGravityParameter;
        EffectParameter effectEmitterPositionParameter;

        // An array of particles, treated as a circular queue.
        ParticleVertex[] particles;


        // A vertex buffer holding our particles. This contains the same data as
        // the particles array, but copied across to where the GPU can access it.
        DynamicVertexBuffer vertexBuffer;
        uint[] indices;

        // Vertex declaration describes the format of our ParticleVertex structure.
        // Not used in 4.0 VertexDeclaration vertexDeclaration;
        IndexBuffer indexBuffer;

        int firstActiveParticle;
        int firstNewParticle;
        int firstFreeParticle;
        int firstRetiredParticle;


        // Store the current time, in seconds.
        float currentTime;


        // Count how many times Draw has been called. This is used to know
        // when it is safe to retire old particles back into the free list.
        int drawCounter;


        // Shared random number generator.
        static Random random = new Random();
        Emitters.Emitter emitterRef;

        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public ParticleSystem(GraphicsDevice graphics, ref Effect peffect, EmitterSettings psettings, Texture2D tex, Emitters.Emitter emitter)
        {
            particleEffect = peffect.Clone();
            settings = psettings;
            texture = tex;

            Initialize(graphics);
            ApplyEffectSettings();
            settings.ApplyBlendOptions();
            emitterRef = emitter;
        }


        /// <summary>
        /// Initializes the component.
        /// </summary>
        public void Initialize(GraphicsDevice graphics)
        {
            particles = new ParticleVertex[settings.MaxParticles * 4];
            for (int i = 0; i < settings.MaxParticles; i++)
            {
                for (int i2 = 0; i2 < 4; i2++)
                {
                    particles[(i * 4) + i2] = new ParticleVertex();
                    particles[(i * 4) + i2].Position = Vector3.Zero;
                    switch (i2)
                    {
                        case 0: particles[(i * 4) + i2].Corner = new Short2(-1, -1); break;
                        case 1: particles[(i * 4) + i2].Corner = new Short2(1, -1); break;
                        case 2: particles[(i * 4) + i2].Corner = new Short2(1, 1); break;
                        case 3: particles[(i * 4) + i2].Corner = new Short2(-1, 1); break;
                    }
                }
            }
            

            vertexBuffer = new DynamicVertexBuffer(graphics, 
                ParticleVertex.VertexDeclaration, 
                (int)settings.MaxParticles * 4, 
                BufferUsage.WriteOnly);
            
            // Create and populate the index buffer.
            indices = new uint[settings.MaxParticles * 6];

            for (int i = 0; i < settings.MaxParticles; i++)
            {
                indices[i * 6 + 0] = (uint)(i * 4 + 0);
                indices[i * 6 + 1] = (uint)(i * 4 + 1);
                indices[i * 6 + 2] = (uint)(i * 4 + 2);

                indices[i * 6 + 3] = (uint)(i * 4 + 0);
                indices[i * 6 + 4] = (uint)(i * 4 + 2);
                indices[i * 6 + 5] = (uint)(i * 4 + 3);
            }

            indexBuffer = new IndexBuffer(graphics, typeof(uint), indices.Length, BufferUsage.WriteOnly);

            indexBuffer.SetData(indices);
            
        }

        public void Dispose()
        {
            particleEffect.Dispose();
            // we won't destroy the texture because 
            // we might be reusing it in a clone of this effect
        }

        /// <summary>
        /// Helper for loading and initializing the particle effect.
        /// </summary>
        public void ApplyEffectSettings()
        {
            EffectParameterCollection parameters = particleEffect.Parameters;

            // Look up shortcuts for parameters that change every frame.
            effectViewParameter = parameters["View"];
            effectProjectionParameter = parameters["Projection"];
            //effectViewportHeightParameter = parameters["ViewportHeight"];
            effectViewportScaleParameter = parameters["ViewportScale"];
            // ViewportScale
            //effectViewportHeightParameter = parameters["ViewportScale"];

            effectTimeParameter = parameters["CurrentTime"];

            effectForcesTimeParameter = parameters["ForcesTime"];
            effectGravityParameter = parameters["Gravity"];
            effectLastGravityParameter = parameters["LastGravity"];
            effectEmitterPositionParameter = parameters["EmitterPosition"];

            // Set the values of parameters that don't typically change.
            parameters["Duration"].SetValue((float)settings.Duration.TotalSeconds);
            parameters["DurationRandomness"].SetValue(settings.DurationRandomness);
            parameters["EndVelocity"].SetValue(settings.EndVelocity);
            parameters["MinColor"].SetValue(settings.MinColor.ToVector4());
            parameters["MaxColor"].SetValue(settings.MaxColor.ToVector4());

            parameters["RotateSpeed"].SetValue(
                new Vector2(settings.MinRotateSpeed, settings.MaxRotateSpeed));

            // parameters["StartRotation"].SetValue((float)random.Next(0, Math.Abs(settings.RotationVariation)));

            // Forces
            Vector4 forcesTime = parameters["ForcesTime"].GetValueVector4();
            effectGravityParameter.SetValue(settings.Gravity);
            effectLastGravityParameter.SetValue(settings.LastGravity);
            if (settings.CheckGravityChange())
            {
                forcesTime.X = currentTime;
                settings.Gravity = settings.Gravity;
            }

            //parameters["Gravity"].SetValue(settings.Gravity);
            parameters["BlackHolePosition"].SetValue(settings.BlackHole_Position);
            parameters["BlackHoleStrength"].SetValue(settings.BlackHole_Strength);
            parameters["LastBlackHoleStrength"].SetValue(settings.BlackHole_Strength);
            if (settings.CheckBlackholeChange())
            {
                forcesTime.Z = currentTime;
                settings.BlackHole_Strength = settings.BlackHole_Strength;
            }

            parameters["RadialGravity"].SetValue(settings.RadialGravity);
            parameters["LastRadialGravity"].SetValue(settings.LastRadialGravity);
            if (settings.CheckRadialGravityChange())
            {
                forcesTime.Y = currentTime;
                settings.RadialGravity = settings.RadialGravity;
            }

            effectForcesTimeParameter.SetValue(forcesTime);


            parameters["StartSize"].SetValue(
                new Vector2(settings.MinStartSize, settings.MaxStartSize));
            
            parameters["EndSize"].SetValue(
                new Vector2(settings.MinEndSize, settings.MaxEndSize));

            // Load the particle texture, and set it onto the effect.
            //Texture2D texture = content.Load<Texture2D>(settings.TextureName);

            parameters["Texture"].SetValue(texture);

            // Color and fade
            parameters["FadeTime"].SetValue(settings.FadeTime);

            parameters["FadeMultiplier"].SetValue(settings.FadeMultiplier);

            //// Choose the appropriate effect technique. If these particles will never
            //// rotate, we can use a simpler pixel shader that requires less GPU power.
            //string techniqueName;

            //if ((settings.MinRotateSpeed == 0) && (settings.MaxRotateSpeed == 0) && (settings.RotationVariation == 0))
            //    techniqueName = "NonRotatingParticles";
            //else if (settings.RotationVariation != 0)
            //    techniqueName = "NonRotatingWithRandomSpawnRotationParticles";
            //else
            //    techniqueName = "RotatingParticles";

            //particleEffect.CurrentTechnique = particleEffect.Techniques[techniqueName];
            particleEffect.CurrentTechnique = particleEffect.Techniques["Particles"];

            _offset = settings.Offset;
        }


        #endregion

        #region ChangeEffectSettings

        public void SetDuration(TimeSpan value)
        {
            particleEffect.Parameters["Duration"].SetValue((float)value.TotalSeconds);
            settings.Duration = value;
        }
        public void SetDurationRandomness(float value)
        {
            particleEffect.Parameters["DurationRandomness"].SetValue(value);
            settings.DurationRandomness = value;
        }
        public void SetGravity(Vector3 value)
        {
            particleEffect.Parameters["Gravity"].SetValue(value);
            settings.Gravity = value;
        }
        public void SetEndVelocity(float value)
        {
            particleEffect.Parameters["EndVelocity"].SetValue(value);
            settings.EndVelocity = value;
        }
        public void SetMinColor(Color value)
        {
            particleEffect.Parameters["MinColor"].SetValue(value.ToVector4());
            settings.MinColor = value;
        }
        public void SetMaxColor(Color value)
        {
            particleEffect.Parameters["MaxColor"].SetValue(value.ToVector4());
            settings.MaxColor = value;
        }
        public void SetRotateSpeed(float min, float max)
        {
            particleEffect.Parameters["RotateSpeed"].SetValue(new Vector2(min, max));
            settings.MinRotateSpeed = min;
            settings.MaxRotateSpeed = max;

            if ((settings.MinRotateSpeed == 0) && (settings.MaxRotateSpeed == 0))
                particleEffect.CurrentTechnique = particleEffect.Techniques["NonRotatingParticles"];
            else
                particleEffect.CurrentTechnique = particleEffect.Techniques["RotatingParticles"];
            
        }
        public void SetStartSize(float min, float max)
        {
            particleEffect.Parameters["StartSize"].SetValue(new Vector2(min, max));
            settings.MinStartSize = min;
            settings.MaxStartSize = max;
        }
        public void SetEndSize(float min, float max)
        {
            particleEffect.Parameters["EndSize"].SetValue(new Vector2(min, max));
            settings.MinEndSize = min;
            settings.MaxEndSize = max;
        }
        public void SetTexture(Texture2D value)
        {
            particleEffect.Parameters["Texture"].SetValue(value);
            texture = value;
        }
        public void SetRotationVariation(int val)
        {
            particleEffect.Parameters["StartRotation"].SetValue((float)random.Next(0, Math.Abs(settings.RotationVariation)));
            settings.RotationVariation = val;
        }

        public void SetBlackHolePosition()
        {
            particleEffect.Parameters["BlackHolePosition"].SetValue(settings.BlackHole_Position);
        }
        public void SetBlackHoleStrength()
        {
            particleEffect.Parameters["BlackHoleStrength"].SetValue(settings.BlackHole_Strength);
        }
        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the particle system.
        /// </summary>
        public void Update(float dt)
        {
            currentTime += dt; //(float)gameTime.ElapsedGameTime.TotalSeconds;

            RetireActiveParticles();
            FreeRetiredParticles();

            // If we let our timer go on increasing for ever, it would eventually
            // run out of floating point precision, at which point the particles
            // would render incorrectly. An easy way to prevent this is to notice
            // that the time value doesn't matter when no particles are being drawn,
            // so we can reset it back to zero any time the active queue is empty.

            if (firstActiveParticle == firstFreeParticle)
                currentTime = 0;

            if (firstRetiredParticle == firstActiveParticle)
                drawCounter = 0;
        }


        /// <summary>
        /// Helper for checking when active particles have reached the end of
        /// their life. It moves old particles from the active area of the queue
        /// to the retired section.
        /// </summary>
        void RetireActiveParticles()
        {
            float particleDuration = (float)settings.Duration.TotalSeconds;

            while (firstActiveParticle != firstNewParticle)
            {
                // Is this particle old enough to retire?
                float particleAge = currentTime - particles[firstActiveParticle * 4].Time;

                if (particleAge < particleDuration)
                    break;

                // Remember the time at which we retired this particle.
                particles[firstActiveParticle * 4].Time = drawCounter;

                // Move the particle from the active to the retired queue.
                firstActiveParticle++;

                if (firstActiveParticle >= settings.MaxParticles)
                    firstActiveParticle = 0;
            }
        }


        /// <summary>
        /// Helper for checking when retired particles have been kept around long
        /// enough that we can be sure the GPU is no longer using them. It moves
        /// old particles from the retired area of the queue to the free section.
        /// </summary>
        void FreeRetiredParticles()
        {
            while (firstRetiredParticle != firstActiveParticle)
            {
                // Has this particle been unused long enough that
                // the GPU is sure to be finished with it?
                int age = drawCounter - (int)particles[firstRetiredParticle * 4].Time;

                // The GPU is never supposed to get more than 2 frames behind the CPU.
                // We add 1 to that, just to be safe in case of buggy drivers that
                // might bend the rules and let the GPU get further behind.
                if (age < 3)
                    break;

                // Move the particle from the retired to the free queue.
                firstRetiredParticle++;

                if (firstRetiredParticle >= settings.MaxParticles)
                    firstRetiredParticle = 0;
            }
        }

        private void SetParameterValues(GraphicsDevice device)
        {
            // Set an effect parameter describing the viewport size. This is needed
            // to convert particle sizes into screen space point sprite sizes.
            //effectViewportHeightParameter.SetValue(device.Viewport.Height);
            effectViewportScaleParameter.SetValue(new Vector2(0.5f / device.Viewport.AspectRatio, -0.5f));

            // Set an effect parameter describing the current time. All the vertex
            // shader particle animation is keyed off this value.
            effectTimeParameter.SetValue(currentTime);

            //effectEmitterPositionParameter.SetValue(emitterRef.Position);
        }

        /// <summary>
        /// Draws the particle system.
        /// </summary>
        public void Draw(float dt, GraphicsDevice device)
        {
            // Restore the vertex buffer contents if the graphics device was lost.
            if (vertexBuffer.IsContentLost)
            {
                vertexBuffer.SetData(particles, 0, particles.Length, SetDataOptions.Discard);
            }

            // If there are any particles waiting in the newly added queue,
            // we'd better upload them to the GPU ready for drawing.
            if (firstNewParticle != firstFreeParticle)
            {
                AddNewParticlesToVertexBuffer();
            }

            // If there are any active particles, draw them now!
            if (firstActiveParticle != firstFreeParticle)
            {
                SetParticleRenderStates(device);

                SetParameterValues(device);

                
                // Set the particle vertex buffer and vertex declaration.
                // 3.1 device.Vertices[0].SetSource(vertexBuffer, 0, ParticleVertex.SizeInBytes);
                device.SetVertexBuffer(vertexBuffer);
                device.Indices = indexBuffer;
                // 3.1 device.VertexDeclaration = vertexDeclaration;

                // Activate the particle effect.
                // 3.1 - No longer needed - particleEffect.Begin();

                for (int i = 0; i < particleEffect.CurrentTechnique.Passes.Count; i++)
                //foreach (EffectPass pass in particleEffect.CurrentTechnique.Passes)
                {
                    // 3.1 particleEffect.CurrentTechnique.Passes[i].Begin();
                    particleEffect.CurrentTechnique.Passes[i].Apply();
                    //pass.Begin();

                    if (firstActiveParticle < firstFreeParticle)
                    {
                        // If the active particles are all in one consecutive range,
                        // we can draw them all in a single call.
                        //device.DrawPrimitives(PrimitiveType.LineList,
                        //                      firstActiveParticle,
                        //                      firstFreeParticle - firstActiveParticle);

                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
                                                     firstActiveParticle * 4, (firstFreeParticle - firstActiveParticle) * 4,
                                                     firstActiveParticle * 6, (firstFreeParticle - firstActiveParticle) * 2);
                    }
                    else
                    {
                        // If the active particle range wraps past the end of the queue
                        // back to the start, we must split them over two draw calls.
                        //device.DrawPrimitives(PrimitiveType.LineList,
                        //                      firstActiveParticle,
                        //                      particles.Length - firstActiveParticle);
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
                                                     firstActiveParticle * 4, (int)(settings.MaxParticles - firstActiveParticle) * 4,
                                                     firstActiveParticle * 6, (int)(settings.MaxParticles - firstActiveParticle) * 2);


                        if (firstFreeParticle > 0)
                        {
                            //device.DrawPrimitives(PrimitiveType.LineList,
                            //                      0,
                            //                      firstFreeParticle);
                            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
                                                         0, firstFreeParticle * 4,
                                                         0, firstFreeParticle * 2);
                        }
                    }

                    //pass.End();
                    //particleEffect.CurrentTechnique.Passes[i].End();
                }

                //particleEffect.End();

                // Reset a couple of the more unusual renderstates that we changed,
                // so as not to mess up any other subsequent drawing.
                //device.RenderState.PointSpriteEnable = false;
                //device.RenderState.DepthBufferWriteEnable = true;
                device.DepthStencilState = DepthStencilState.Default;
            }

            drawCounter++;
        }


        /// <summary>
        /// Helper for uploading new particles from our managed
        /// array to the GPU vertex buffer.
        /// </summary>
        void AddNewParticlesToVertexBuffer()
        {
            int stride = ParticleVertex.SizeInBytes;

            if (firstNewParticle < firstFreeParticle)
            {
                // If the new particles are all in one consecutive range,
                // we can upload them all in a single call.
                vertexBuffer.SetData(firstNewParticle * stride * 4, particles,
                                     firstNewParticle * 4,
                                     (firstFreeParticle - firstNewParticle) * 4,
                                     stride, SetDataOptions.NoOverwrite);
            }
            else
            {
                // If the new particle range wraps past the end of the queue
                // back to the start, we must split them over two upload calls.
                vertexBuffer.SetData(firstNewParticle * stride * 4, particles,
                                     firstNewParticle * 4,
                                     ((int)settings.MaxParticles - firstNewParticle) * 4,
                                     stride, SetDataOptions.NoOverwrite);

                if (firstFreeParticle > 0)
                {
                    vertexBuffer.SetData(0, particles,
                                         0, firstFreeParticle * 4,
                                         stride, SetDataOptions.NoOverwrite);
                }
            }

            // Move the particles we just uploaded from the new to the active queue.
            firstNewParticle = firstFreeParticle;
        }


        /// <summary>
        /// Helper for setting the renderstates used to draw particles.
        /// </summary>
        void SetParticleRenderStates(GraphicsDevice device)
        {
            if (settings.BlendState != null)
                device.BlendState = settings.BlendState;
            else
                device.BlendState = BlendState.Additive;

            //device.BlendState = BlendState.Additive;

            device.DepthStencilState = DepthStencilState.DepthRead;

#region Old 3.1 Code

            // Enable point sprites. (*** doesn't work in 4.0 ***)
            //renderState.PointSpriteEnable = true;
            //renderState.PointSizeMax = 256;

            // Set the alpha blend mode.
            //BlendState bState = new BlendState();
            //bState.AlphaBlendFunction = settings.AlphaBlendOperation;
            //bState.ColorBlendFunction = settings.BlendOperation;
            //bState.ColorSourceBlend = settings.SourceBlend;
            //bState.ColorDestinationBlend = settings.DestinationBlend;
            
            // 3.1 blendState.AlphaBlendEnable = true;
            //device.BlendState = BlendState.AlphaBlend;
            
            // 3.1 renderState.AlphaBlendOperation = settings.AlphaBlendOperation;
            //device.BlendState.AlphaBlendFunction = settings.AlphaBlendOperation;

            // 3.1 renderState.BlendFunction = settings.BlendOperation;
            //device.BlendState.ColorBlendFunction = settings.BlendOperation;

            // 3.1 renderState.SourceBlend = settings.SourceBlend;
            //device.BlendState.ColorSourceBlend = settings.SourceBlend;

            // 3.1 renderState.DestinationBlend = settings.DestinationBlend;
            //device.BlendState.ColorDestinationBlend = settings.DestinationBlend;

            // Set the alpha test mode. (*** Not in 4.0 ***)
            //renderState.AlphaTestEnable = true;
            //renderState.AlphaFunction = settings.AlphaFunction; // CompareFunction.Greater;
            //renderState.ReferenceAlpha = 0;

            // Enable the depth buffer (so particles will not be visible through
            // solid objects like the ground plane), but disable depth writes
            // (so particles will not obscure other particles).
            
            // 3.1 renderState.DepthBufferEnable = true;
            // 3.1 renderState.DepthBufferWriteEnable = false;

            //device.DepthStencilState.DepthBufferWriteEnable = false;
#endregion

        }


        #endregion

        #region Public Methods


        /// <summary>
        /// Sets the camera view and projection matrices
        /// that will be used to draw this particle system.
        /// </summary>
        public void SetCamera(Matrix view, Matrix projection)
        {
            effectViewParameter.SetValue(view);
            effectProjectionParameter.SetValue(projection);
        }


        /// <summary>
        /// Adds a new particle to the system.
        /// </summary>
        public void AddParticle(Vector3 position, Vector3 velocity)
        {
            // Figure out where in the circular queue to allocate the new particle.
            int nextFreeParticle = firstFreeParticle + 1;

            if (nextFreeParticle >= settings.MaxParticles)
                nextFreeParticle = 0;

            // If there are no free particles, we just have to give up.
            if (nextFreeParticle == firstRetiredParticle)
                return;

            // Adjust the input velocity based on how much
            // this particle system wants to be affected by it.
            velocity *= settings.EmitterVelocitySensitivity;

            
                // Add in some random amount of horizontal velocity.
            float horizontalVelocity = MathHelper.Lerp(settings.MinHorizontalVelocity,
                                                           settings.MaxHorizontalVelocity,
                                                           (float)random.NextDouble());

            if (velocity.X != 0)
            {
                double horizontalAngle = random.NextDouble() * MathHelper.TwoPi;

                velocity.X += horizontalVelocity * (float)Math.Cos(horizontalAngle);

                if (!ConstrainZ)
                    velocity.Z += horizontalVelocity * (float)Math.Sin(horizontalAngle);
                else
                    velocity.Z = 0;
            }
            // Add in some random amount of vertical velocity.
            if (velocity.Y != 0)
            {
                velocity.Y += MathHelper.Lerp(settings.MinVerticalVelocity,
                                              settings.MaxVerticalVelocity,
                                              (float)random.NextDouble());
            }
            // Choose four random control values. These will be used by the vertex
            // shader to give each particle a different size, rotation, and color.
            Color randomValues = new Color((byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255));

            // Fill in the particle vertex structure.
            for (int i = 0; i < 4; i++)
            {
                particles[firstFreeParticle * 4 + i].Position = position;
                particles[firstFreeParticle * 4 + i].Velocity = velocity;
                particles[firstFreeParticle * 4 + i].Random = randomValues;
                particles[firstFreeParticle * 4 + i].Time = currentTime;
                particles[firstFreeParticle * 4 + i].EmitterPosition = emitterRef.Position;
            }
            

            firstFreeParticle = nextFreeParticle;
        }

        /// <summary>
        /// Adds a new particle to the system without modifying the velocity
        /// </summary>
        /// <param name="position"></param>
        /// <param name="velocity"></param>
        public void AddParticleManualVelocity(Vector3 position, Vector3 velocity)
        {
            // Figure out where in the circular queue to allocate the new particle.
            int nextFreeParticle = firstFreeParticle + 1;

            if (nextFreeParticle >= settings.MaxParticles)
                nextFreeParticle = 0;

            // If there are no free particles, we just have to give up.
            if (nextFreeParticle == firstRetiredParticle)
                return;

            // Choose four random control values. These will be used by the vertex
            // shader to give each particle a different size, rotation, and color.
            Color randomValues = new Color((byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255));

            if (settings.RotationVariation != 0)
            {
                randomValues.A = (byte)(Utils.NumberGenerator.GenerateRandomInteger(0, settings.RotationVariation));
            }

            // Apply the offset
            Vector3.Add(ref position, ref _offset, out position);
            // Fill in the particle vertex structure.
            for (int i = 0; i < 4; i++)
            {
                particles[firstFreeParticle * 4 + i].Position = position;
                particles[firstFreeParticle * 4 + i].Velocity = velocity;
                particles[firstFreeParticle * 4 + i].Random = randomValues;
                particles[firstFreeParticle * 4 + i].Time = currentTime;
                particles[firstFreeParticle * 4 + i].EmitterPosition = emitterRef.Position;
            }

            firstFreeParticle = nextFreeParticle;
        }

        public void ClearParticles()
        {
            firstFreeParticle = 0;
            firstActiveParticle = 0;
            firstNewParticle = 0;
            firstRetiredParticle = 0;
        }
        #endregion
    }
}
